iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Build on Google AI

從 Vibe Coding 到 Production:用 Google AI 打造上線守門員系列 第 22

Day 22|單一 Agent 不夠用:設計偵察、檢查與驗證的多 Agent 流程

  • 分享至 

  • xImage
  •  

Day 21,我們沒有把整個 Repository 全部塞進 Prompt,而是根據 SEC-AUTHZ-001 的必要證據建立 context.json

Agent 現在取得:

Firestore 授權規則
Project 的 ownerId 寫入位置
projects Collection 的讀取路徑
Browser 到 Firestore 的信任邊界

接下來就能交給 Gemini 找問題了嗎?

如果使用單一 Agent,Prompt 可能變成:

請先理解系統架構,
選擇適用規則,
尋找 Production 問題,
執行必要測試,
推翻可能的誤報,
決定嚴重度,
最後輸出報告。

這段 Prompt 把研究、懷疑、驗證與裁決全部交給同一個角色。

模型一旦在前面形成:

這裡存在 Broken Access Control。

後面的驗證就容易變成尋找支持原判斷的證據,而不是認真嘗試推翻它。

今天要把 Vibe Guard 拆成第一版多 Agent Workflow:

Orchestrator
  → Recon Agent
  → Context Agent
  → Hunter Agent
  → Validator Agent
  → Reporter Agent

每個 Agent 都只有一項主要責任、明確輸入、固定輸出與有限權限。

其中最重要的規則是:

Hunter 只能提出 Candidate,不能自行把它升級為 Confirmed Finding。

單一 Agent 的問題不只是 Prompt 太長

把所有工作交給同一個 Agent,確實會讓 System Instruction 越來越長。

但更大的問題是角色互相衝突。

Recon 的目標是建立中立的系統模型:

有哪些元件?
資料如何流動?
授權在哪裡執行?
哪些資訊仍未知?

Hunter 的目標則是主動懷疑:

攻擊者能控制什麼?
哪條路徑可能跨越邊界?
缺少哪個控制可能造成影響?

Validator 必須採取相反立場:

這條路徑真的可達嗎?
是否有其他控制阻擋?
測試能否重現?
原始碼引用是否正確?

Reporter 又有另一種任務:

哪些結果通過門檻?
如何排序與呈現?
哪些 Candidate 被拒絕?

如果同一段對話同時承擔這些目標,會出現幾種風險。

第一,Confirmation Bias。

模型提出 Candidate 後,後續容易延續自己的推理,而不是重新質疑前提。

第二,權限過大。

負責讀程式碼的 Agent,不一定需要執行 Shell。

負責輸出報告的 Agent,更不應擁有修改 Repository 或部署環境的權限。

第三,Context 汙染。

Recon 的大量架構資訊、Hunter 的假設、測試 Log 與最終報告格式全部留在同一段 History,會讓每個階段都背負不需要的 Token 與不可信文字。

第四,失敗難以定位。

結果錯誤時,很難分辨是:

Recon 漏掉信任邊界
Context Selector 漏掉必要檔案
Hunter 沒有提出 Candidate
Validator 沒有執行正確測試
Reporter 改寫了 Verdict

第五,輸出沒有穩定的交接契約。

前一階段用 Markdown 說明,下一階段只能再請模型自行解讀,錯誤會沿著 Workflow 放大。

多 Agent 的價值不是讓更多模型同時說話,而是把責任、Context、工具與錯誤邊界拆開。

Agent 與 Workflow 是不同責任

Agent 負責在有限範圍內完成一項工作。

Workflow 負責決定:

誰先執行?
誰可以讀取哪些 Artifact?
哪個結果能進入下一步?
何時停止?
失敗後可以重試還是必須終止?

這種控制流程不一定要交給 LLM。

Google Agent Development Kit 的 Workflow 文件將 Predictability、Reliability、Structure 與限制各 Agent 的 Context,列為組合多個 Agent 與執行節點的主要優點。

ADK 支援幾種 Workflow 結構:

Workflow 適合情境
Sequential 有固定先後相依,例如先 Recon 再 Hunt
Parallel 工作彼此獨立,例如 Security、Privacy、Reliability Hunter
Loop 反覆補證據,直到完成或達到停止條件
Graph-based 需要分支、合併、失敗路徑與條件轉移
Collaborative 由 Coordinator 動態選擇 Sub-agent

Day 22 使用固定的 Sequential Workflow。

原因是目前每一步都有清楚相依:

Recon 沒完成
  → 無法判斷規則是否適用

Context 不完整
  → Hunter 不應開始

沒有 Candidate
  → Validator 沒有驗證目標

Validator 未接受
  → Reporter 不得列為 Finding

這種流程若讓模型自由決定執行順序,反而增加不必要的不確定性。

因此第一版 Orchestrator 使用 Deterministic JavaScript,而不是再建立一個可以跳過步驟的 LLM Agent。

先定義五個角色

第一個角色是 Recon Agent。

它只負責輸出系統事實:

Application Type
Actor
Input Surface
Data Flow
Trust Boundary
Authorization Control
Unknown

Recon Agent 不輸出漏洞名稱與嚴重度。

第二個角色是 Context Agent。

它根據 Rule 的 requiredEvidence 選取:

相關架構事實
Source File
Symbol 或行號範圍
已排除內容
證據覆蓋率

Context Agent 不判斷程式安全與否。

第三個角色是 Hunter Agent。

它使用 Context 建立 Candidate:

{
  "id": "AUTH-01",
  "ruleId": "SEC-AUTHZ-001",
  "verdict": "candidate",
  "hypothesis": "Any authenticated user may read and modify another user's project.",
  "evidence": [],
  "requestedValidation": []
}

Hunter 可以大膽提出攻擊假設,但 verdict 只能是 candidate

第四個角色是 Validator Agent。

它不能只接受 Hunter 的文字結論,而要:

  1. 重新讀取原始檔案。
  2. 核對 Path、Line 與 Snippet。
  3. 檢查 Candidate 是否跨越真實信任邊界。
  4. 執行雙帳號 Firestore Emulator 測試。
  5. 確認 Read、Write 與 Impact。
  6. 依 Day 17 的證據門檻決定 Verdict。

只有 Validator 可以輸出:

confirmed
requires_evidence
hardening
rejected

第五個角色是 Reporter Agent。

它只能讀取 Validator 接受的 Review,不能把被拒絕的 Candidate 改寫成漏洞,也不能自行提高 Severity。

它負責:

計算 Confirmed Finding
依嚴重度排序
產生摘要
連結 Evidence 與 Artifact
設定整體 Workflow Status

Orchestrator 不應只是一個會轉傳文字的聊天機器人

Day 22 的 Orchestrator 使用固定狀態轉移:

START
  → RECON_COMPLETE
  → CONTEXT_READY
  → CANDIDATE_CREATED
  → VALIDATION_COMPLETE
  → REPORT_READY
  → DONE

每一步都必須先通過 Schema 與 Business Rule。

概念上可以寫成:

const reconArtifact = await runReconAgent();
const contextArtifact = await runContextAgent(reconArtifact);
const candidateArtifact = runHunterAgent(contextArtifact);
const validationArtifact = await runValidatorAgent(candidateArtifact);
const reportArtifact = runReporterAgent(validationArtifact);

Orchestrator 不負責重新解釋內容。

它只負責:

傳入允許的 Artifact
檢查輸出契約
記錄 Timeline
處理 Timeout 與錯誤
決定是否進入下一個 Node

這可以避免 Coordinator 自己成為另一個擁有所有 Context、所有工具與所有判斷權的超級 Agent。

不要直接把整段對話當成 Agent 間的 API

最簡單的 Multi-agent Demo 常這樣設計:

Agent A 寫一段 Markdown
Agent B 讀完整聊天紀錄
Agent C 再閱讀 A 與 B 的所有思考

這種方法很快,卻有三個問題。

第一,無法保證下一個 Agent 取得必要欄位。

第二,不相關內容也會被一起傳遞。

第三,無法區分資料、指令與 Agent 的自由文字。

Day 22 改用 Artifact 交接。

Recon Artifact 的最小格式為:

const reconArtifactSchema = z.object({
  application: z.string().min(1),
  trustBoundary: z.string().min(1),
  authorizationControl: evidenceSchema,
  unknowns: z.array(z.string())
}).strict();

Hunter Artifact 則限制:

const candidateArtifactSchema = z.object({
  id: z.literal("AUTH-01"),
  ruleId: z.literal("SEC-AUTHZ-001"),
  verdict: z.literal("candidate"),
  hypothesis: z.string().min(1),
  evidence: z.array(evidenceSchema).min(1),
  requestedValidation: z.array(z.string()).min(1)
}).strict();

z.literal("candidate") 是一道重要邊界。

即使 Hunter 認為問題十分明顯,它也不能輸出:

{
  "verdict": "confirmed"
}

這不是靠 Prompt 中的「請不要」維持,而是由 Runtime Schema 拒絕。

共享 State 與 Artifact 要分開

Workflow 仍然需要 State,例如:

目前執行到哪一步
Run ID
開始時間
已使用 Token
重試次數
整體 Status

但不應把所有大型原始碼、模型回答與測試 Log 都塞進同一個 Mutable State。

比較清楚的分工是:

類型 範例
State currentStepstatusretryCount
Artifact architecture.jsoncontext.jsoncandidate.json
Event Agent 開始、工具呼叫、驗證失敗、狀態轉移
Secret API Key、短期 Credential,不寫入 Artifact

Google ADK 將 Session State 描述為可序列化的 Scratchpad,並區分 Session、User、App 與單次 Invocation 的 Temporary State。

無論使用哪個 Framework,都應避免在共享 State 放入:

Database Connection
Function
SDK Client
未遮罩的 Secret
無限制成長的完整對話

Day 22 Demo 把每階段結果寫成獨立 JSON:

multi-agent-demo/output/
├── candidate.json
├── validated-review.json
└── workflow-run.json

這些檔案可以分別回答:

Hunter 原本提出什麼?
Validator 接受了哪些證據?
Workflow 依什麼順序完成?

工具權限也要依角色切割

多 Agent 如果共用同一組高權限工具,只是把風險複製多份。

第一版可以使用以下權限表:

Agent Read Source Search Execute Test Write Source Publish Report
Recon
Context
Hunter 僅選定 Context
Validator 受限測試
Reporter 僅驗證結果

Hunter 不需要 Shell。

Reporter 不需要讀取整個 Repository。

Validator 的測試工具也不應等同任意命令執行,而應是受限 Function:

run_authorization_test({
  rulesPath,
  scenarioId
})

Gemini Function Calling 可以讓模型選擇工具並產生符合 Function Schema 的參數,但真正執行工具的 Host Application 仍要驗證:

工具名稱是否允許?
Path 是否位於 Repository?
Scenario 是否存在 Allowlist?
是否需要網路?
是否會修改資料?
Timeout 與輸出上限是多少?

模型要求呼叫 Function,不等於模型自動取得 Function 背後的所有權限。

實作 Day 22 Demo

專案新增:

demo-app/
└── multi-agent-demo/
    ├── scenario.js
    └── output/
        ├── candidate.json
        ├── validated-review.json
        └── workflow-run.json

執行:

cd /media/mickey/777/ithome/demo-app
npm run multi-agent:demo

Script 會先執行 Day 18 與 Day 21 的產物流程:

recon-demo/architecture.json
context-selection-demo/context.json

接著啟動 Firestore Emulator,執行 Multi-agent Workflow。

package.json 的指令為:

{
  "scripts": {
    "multi-agent:demo": "npm run context-selection:demo >/dev/null && firebase emulators:exec --only firestore \"node multi-agent-demo/scenario.js\""
  }
}

這裡刻意讓 Emulator 生命週期由 firebase emulators:exec 管理。

測試完成後 Emulator 會停止,不需要讓 Validator 取得管理背景程序的權限。

Recon Agent:只交付架構事實

Recon Agent 讀取 architecture.json,選出 Firestore Trust Boundary:

const firestoreBoundary = architecture.trustBoundaries.find(
  (boundary) => boundary.to === "Cloud Firestore emulator"
);

輸出內容只有:

Local-only Firebase web application
Browser -> Cloud Firestore emulator
firestore.rules:6
4 個 Repository 外的 Unknown

它不會因為看到 request.auth != null 就產生 Finding。

Recon 的完成條件是系統模型可供後續使用,不是找到問題。

Context Agent:必要證據不足就停止

Context Agent 接收 Recon Artifact,再讀取 Day 21 的 Context Bundle。

它會先確認兩份 Artifact 對授權控制的描述一致:

if (
  reconArtifact.authorizationControl.path !==
  bundle.architecture.authorization.path
) {
  throw new Error(
    "Context Agent received conflicting authorization evidence"
  );
}

接著驗證三項 Coverage:

firestore.rules
project ownership field
read and write query path

缺少任何一項就停止 Workflow。

這裡不能使用:

Context 大概夠了,讓 Hunter 自己推測。

因為 Hunter 的任務是尋找問題,不是補完被 Retrieval 漏掉的世界觀。

Hunter Agent:只能提出 Candidate

Hunter 只能看到 Context Agent 選定的兩個 Source File。

它找到:

allow read, write: if request.auth != null;

以及:

ownerId: auth.currentUser.uid

與:

const projectsQuery = query(collection(db, "projects"));

因此提出:

{
  "id": "AUTH-01",
  "ruleId": "SEC-AUTHZ-001",
  "verdict": "candidate",
  "hypothesis": "Any authenticated user may read and modify a project owned by another account.",
  "requestedValidation": [
    "Use two authenticated accounts.",
    "Read a project owned by the other account.",
    "Modify a project owned by the other account."
  ]
}

這份 Artifact 同時說明「為什麼懷疑」與「下一步如何證明」。

它仍然不是最終報告。

Validator Agent:不要相信 Hunter 的自信

Validator 不使用 Hunter 的 Confidence 作為證據。

它重新讀取:

firestore.rules

再建立兩個測試身分:

const ownerDb = testEnvironment
  .authenticatedContext("owner-user")
  .firestore();

const attackerDb = testEnvironment
  .authenticatedContext("attacker-user")
  .firestore();

測試資料明確保存:

{
  "ownerId": "owner-user"
}

接著執行:

owner-user 讀取自己的 Project
attacker-user 讀取 owner-user 的 Project
attacker-user 修改 owner-user 的 Project

只有三項結果都符合攻擊假設,才建立 confirmed Review:

const confirmed =
  ownerRead.allowed &&
  attackerRead.allowed &&
  attackerWrite.allowed;

最後仍要通過 Day 20 的:

await validateReview(review, repositoryRoot);

這會再次核對 Structured Output、Evidence Path、Line、Snippet 與 Confirmed Finding 的必要欄位。

多 Agent 不代表可以移除既有 Validator。

相反地,每個 Agent Boundary 都應該增加可驗證契約。

Reporter 不得重新裁決

Reporter 只讀取:

validationArtifact.review.findings

如果 Validator 沒有接受 Finding,Reporter 必須輸出:

No confirmed findings.

它不能因為 Hunter 的 Candidate 看起來嚴重,就把它補回報告。

目前整體狀態使用:

沒有 Confirmed Finding → passed
至少一個 Confirmed Finding → failed
Workflow 無法完成 → error

failederror 必須分開。

failed 表示檢查成功完成,而且找到不符合上線門檻的問題。

error 表示 Agent、工具、Schema、Timeout 或 Artifact 發生錯誤,這次沒有產生可信結論。

不能把 Error 改寫成:

{
  "findings": []
}

否則 Pipeline 會把「沒有完成檢查」誤認成「檢查通過」。

Demo 執行結果

實際 Workflow 輸出:

MULTI-AGENT PRODUCTION READINESS WORKFLOW

Orchestrator     START    fixed sequential workflow
Recon Agent      PASS     1 trust boundary, 4 unknowns
Context Agent    PASS     2 source files cover 3 requirements
Hunter Agent     CANDIDATE AUTH-01 requests 3 checks
Validator Agent  CONFIRMED AUTH-01 passed source and two-account execution checks
Reporter Agent   PASS     1 confirmed finding
Orchestrator     DONE     workflow status=failed

EXECUTION EVIDENCE
owner-user reads own project:     ALLOWED
attacker-user reads owner project: ALLOWED
attacker-user edits owner project: ALLOWED

FINAL REPORT
HIGH AUTH-01: Any signed-in user can access another user's project
Artifacts: multi-agent-demo/output/

結果中最容易誤解的是:

Reporter Agent PASS
Orchestrator workflow status=failed

Reporter PASS 代表它成功完成自己的工作。

整體 failed 則代表 Production Readiness Gate 找到一項已確認的 High Finding。

Agent 執行狀態與產品檢查結果是兩個不同維度。

哪些工作可以平行?

Day 22 先使用 Sequential Workflow,但完整 Vibe Guard 不必所有工作都排隊。

完成 Recon 後,可以平行執行:

Security Hunter
Privacy Hunter
Reliability Hunter
Deployment Hunter
AI Safety Hunter

前提是它們:

  1. 不依賴彼此尚未產生的結果。
  2. 使用獨立 Context。
  3. 不同時修改共享 Mutable State。
  4. 輸出到不同 Artifact。
  5. 最後有明確 Merge Node。

Google ADK 的 Parallel Workflow 文件也提醒,平行 Sub-agent 之間不會自動共享對話與狀態;若需要協調,必須明確設計 Shared Context、External State 或後處理。

因此不能假設:

Security Hunter 發現 ownerId,
Privacy Hunter 自然就會知道。

如果兩者需要共用資料,應由 Recon Artifact 或後續 Merge Node 提供,而不是依靠平行執行時的隱性溝通。

何時需要 Loop?

有些 Candidate 第一次沒有足夠證據:

缺少部署環境設定
缺少 SDK Timeout 預設值
缺少 IAM Policy
缺少動態測試

可以設計:

Hunter
  → Validator
  → Evidence Request
  → Context Agent 補資料
  → Validator 再判斷

但 Loop 必須有停止條件:

最多重試 2 次
每次必須要求新的具體證據
Context Token 不得超過 Budget
同一工具錯誤不得無限重試
Repository 無法回答時輸出 requires_evidence

如果沒有停止條件,Agent 可能重複搜尋同一個檔案,直到耗盡 Token、時間或 API Quota。

失敗策略要在 Workflow 中定義

不同錯誤不應使用相同處理方式。

失敗 建議處理
JSON 不符合 Schema 同一步有限次重試
Evidence 行號過期 重新 Retrieval,不接受舊 Finding
測試 Timeout 標記 Error,保存 Log
缺少 Repository 外設定 requires_evidence
Candidate 被反證 rejected
Agent 無權限呼叫工具 拒絕並記錄 Policy Event
API 暫時性錯誤 帶 Backoff 的有限次重試

最重要的是保留 Fail Closed 的語意。

Production Readiness Workflow 無法確認結果時,不應自動回傳:

Ready for production.

可以回傳:

Assessment incomplete.
Missing evidence: production IAM policy.

是否每個角色都需要不同模型?

不一定。

Multi-agent 是責任與執行邊界,不等於一定要使用五個不同 Model。

可以依任務選擇:

Recon:較快模型 + Deterministic Parser
Context:Search、AST、Code Graph,模型只負責分類
Hunter:擅長跨檔案推理的模型
Validator:Deterministic Test + 獨立模型覆核
Reporter:較快模型或純程式模板

甚至部分 Node 完全不需要 LLM。

Day 22 的 Orchestrator、Schema Validation、Source Evidence Check 與 Firestore Test 都是 Deterministic Code。

Gemini 最適合負責:

理解不同技術棧
提出攻擊假設
解釋跨檔案資料流
把缺少的證據轉成具體 Tool Request

不是負責取代所有可以精確執行的程式。

第一版還缺少什麼?

今天完成的是單一規則、單一 Candidate 的 Sequential Workflow。

正式版本還需要:

  • 為每次執行建立 Run ID 與 Artifact Version。
  • 記錄每個 Agent 的 Model、Prompt、Token 與延遲。
  • 加入 Timeout、Cancellation 與 Retry Policy。
  • 使用不可變 Artifact,避免後一階段改寫前一階段內容。
  • 平行執行不同 Domain Hunter。
  • 為高風險 Finding 加入人工核准。
  • 限制工具的 Path、Network、CPU、Memory 與輸出大小。
  • 對 Agent 間 Artifact 執行 Prompt Injection 掃描與資料分級。
  • 建立 Workflow Replay,讓相同 Artifact 可以重跑 Validator。
  • 測試 Agent 或工具失敗時是否安全停止。

目前 Validator 已與 Hunter 分離,但仍使用 Hunter 選出的 Source Evidence。

明天會更進一步:讓另一個 Agent 採用對抗立場與獨立 Retrieval,主動尋找反證,而不是只執行 Hunter 指定的驗證步驟。

今天的結論

多 Agent 的重點不是 Agent 數量,而是清楚回答:

  1. 每個角色唯一的責任是什麼?
  2. 它可以看到哪些 Context?
  3. 它能使用哪些工具與權限?
  4. 輸入與輸出使用什麼 Schema?
  5. 誰有權改變 Verdict?
  6. 失敗、拒絕與檢查不通過如何區分?
  7. Artifact 如何保存、重播與追蹤?
  8. 哪些步驟應固定順序,哪些可以平行?

今天的 Vibe Guard Workflow 使用:

Recon Agent
  → 建立 Firestore Trust Boundary

Context Agent
  → 確認三項必要證據完整

Hunter Agent
  → 提出 AUTH-01 Candidate

Validator Agent
  → 重新核對 Source 並執行雙帳號測試

Reporter Agent
  → 只輸出 Validator 接受的 Finding

最後,attacker-user 成功讀取並修改 owner-user 的 Project。

因此 Validator 將 Candidate 升級為 High Confirmed Finding,而 Orchestrator 將 Production Readiness Workflow 標記為 failed

這份結果不是同一個 Agent 對自己說「我同意我的判斷」,而是經過角色隔離、Artifact 契約與動態測試的可追蹤流程。

明天,我們會讓另一個 Agent 專門挑錯,使用對抗驗證淘汰假警報。

參考資料


上一篇
Day 21|上下文不可能無限大:如何提供 Agent 正確的程式碼資訊
下一篇
Day 23|讓另一個 Agent 挑錯:用對抗驗證淘汰假警報
系列文
從 Vibe Coding 到 Production:用 Google AI 打造上線守門員23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言